﻿using System;
using GeoAPI.Geometries;
using GeoAPI.Operation.Buffer;
using NetTopologySuite.Algorithm;
using NetTopologySuite.Geometries;
using NetTopologySuite.GeometriesGraph;

namespace NetTopologySuite.Operation.Buffer
{
	internal class OffsetSegmentGenerator
	{
		public OffsetSegmentGenerator(IPrecisionModel precisionModel, IBufferParameters bufParams, double distance)
		{
			this._precisionModel = precisionModel;
			this._bufParams = bufParams;
			this._li = new RobustLineIntersector();
			this._filletAngleQuantum = System.Math.PI*.5 / (double)bufParams.QuadrantSegments;
			if (bufParams.QuadrantSegments >= 8 && bufParams.JoinStyle == JoinStyle.Round)
			{
				this._closingSegLengthFactor = 80;
			}
			this.Init(distance);
		}

		public bool HasNarrowConcaveAngle
		{
			get
			{
				return this._hasNarrowConcaveAngle;
			}
		}

		private void Init(double distance)
		{
			this._distance = distance;
			this._maxCurveSegmentError = distance * (1.0 - Math.Cos(this._filletAngleQuantum / 2.0));
			this._segList = new OffsetSegmentString();
			this._segList.PrecisionModel = this._precisionModel;
			this._segList.MinimumVertexDistance = distance * 1E-06;
		}

		public void InitSideSegments(Coordinate s1, Coordinate s2, Positions side)
		{
			this._s1 = s1;
			this._s2 = s2;
			this._side = side;
			this._seg1.SetCoordinates(s1, s2);
			OffsetSegmentGenerator.ComputeOffsetSegment(this._seg1, side, this._distance, this._offset1);
		}

		public Coordinate[] GetCoordinates()
		{
			return this._segList.GetCoordinates();
		}

		public void CloseRing()
		{
			this._segList.CloseRing();
		}

		public void AddSegments(Coordinate[] pt, bool isForward)
		{
			this._segList.AddPts(pt, isForward);
		}

		public void AddFirstSegment()
		{
			this._segList.AddPt(this._offset1.P0);
		}

		public void AddLastSegment()
		{
			this._segList.AddPt(this._offset1.P1);
		}

		public void AddNextSegment(Coordinate p, bool addStartPoint)
		{
			this._s0 = this._s1;
			this._s1 = this._s2;
			this._s2 = p;
			this._seg0.SetCoordinates(this._s0, this._s1);
			OffsetSegmentGenerator.ComputeOffsetSegment(this._seg0, this._side, this._distance, this._offset0);
			this._seg1.SetCoordinates(this._s1, this._s2);
			OffsetSegmentGenerator.ComputeOffsetSegment(this._seg1, this._side, this._distance, this._offset1);
			if (this._s1.Equals(this._s2))
			{
				return;
			}
			int num = CGAlgorithms.ComputeOrientation(this._s0, this._s1, this._s2);
			bool flag = (num == -1 && this._side == Positions.Left) || (num == 1 && this._side == Positions.Right);
			if (num == 0)
			{
				this.AddCollinear(addStartPoint);
				return;
			}
			if (flag)
			{
				this.AddOutsideTurn(num, addStartPoint);
				return;
			}
			this.AddInsideTurn(num, addStartPoint);
		}

		private void AddCollinear(bool addStartPoint)
		{
			this._li.ComputeIntersection(this._s0, this._s1, this._s1, this._s2);
			if (this._li.IntersectionNum >= 2)
			{
				if (this._bufParams.JoinStyle == JoinStyle.Bevel || this._bufParams.JoinStyle == JoinStyle.Mitre)
				{
					if (addStartPoint)
					{
						this._segList.AddPt(this._offset0.P1);
					}
					this._segList.AddPt(this._offset1.P0);
					return;
				}
				this.AddFillet(this._s1, this._offset0.P1, this._offset1.P0, -1, this._distance);
			}
		}

		private void AddOutsideTurn(int orientation, bool addStartPoint)
		{
			if (this._offset0.P1.Distance(this._offset1.P0) < this._distance * 0.001)
			{
				this._segList.AddPt(this._offset0.P1);
				return;
			}
			if (this._bufParams.JoinStyle == JoinStyle.Mitre)
			{
				this.AddMitreJoin(this._s1, this._offset0, this._offset1, this._distance);
				return;
			}
			if (this._bufParams.JoinStyle == JoinStyle.Bevel)
			{
				this.AddBevelJoin(this._offset0, this._offset1);
				return;
			}
			if (addStartPoint)
			{
				this._segList.AddPt(this._offset0.P1);
			}
			this.AddFillet(this._s1, this._offset0.P1, this._offset1.P0, orientation, this._distance);
			this._segList.AddPt(this._offset1.P0);
		}

		private void AddInsideTurn(int orientation, bool addStartPoint)
		{
			this._li.ComputeIntersection(this._offset0.P0, this._offset0.P1, this._offset1.P0, this._offset1.P1);
			if (this._li.HasIntersection)
			{
				this._segList.AddPt(this._li.GetIntersection(0));
				return;
			}
			this._hasNarrowConcaveAngle = true;
			if (this._offset0.P1.Distance(this._offset1.P0) < this._distance * 0.001)
			{
				this._segList.AddPt(this._offset0.P1);
				return;
			}
			this._segList.AddPt(this._offset0.P1);
			if (this._closingSegLengthFactor > 0)
			{
				Coordinate pt = new Coordinate(((double)this._closingSegLengthFactor * this._offset0.P1.X + this._s1.X) / (double)(this._closingSegLengthFactor + 1), ((double)this._closingSegLengthFactor * this._offset0.P1.Y + this._s1.Y) / (double)(this._closingSegLengthFactor + 1));
				this._segList.AddPt(pt);
				Coordinate pt2 = new Coordinate(((double)this._closingSegLengthFactor * this._offset1.P0.X + this._s1.X) / (double)(this._closingSegLengthFactor + 1), ((double)this._closingSegLengthFactor * this._offset1.P0.Y + this._s1.Y) / (double)(this._closingSegLengthFactor + 1));
				this._segList.AddPt(pt2);
			}
			else
			{
				this._segList.AddPt(this._s1);
			}
			this._segList.AddPt(this._offset1.P0);
		}

		private static void ComputeOffsetSegment(LineSegment seg, Positions side, double distance, LineSegment offset)
		{
            object obj = (side == Positions.Left) ? 1 : -1;
            double num = seg.P1.X - seg.P0.X;
            double num2 = seg.P1.Y - seg.P0.Y;
            double num3 = System.Math.Sqrt(num * num + num2 * num2);
            object obj2 = obj;
            double num4 = (int)obj2 * distance * num / num3;
            double num5 = (int)obj2 * distance * num2 / num3;
            offset.P0.X = seg.P0.X - num5;
            offset.P0.Y = seg.P0.Y + num4;
            offset.P1.X = seg.P1.X - num5;
            offset.P1.Y = seg.P1.Y + num4;
		}

		public void AddLineEndCap(Coordinate p0, Coordinate p1)
		{
			LineSegment seg = new LineSegment(p0, p1);
			LineSegment lineSegment = new LineSegment();
			OffsetSegmentGenerator.ComputeOffsetSegment(seg, Positions.Left, this._distance, lineSegment);
			LineSegment lineSegment2 = new LineSegment();
			OffsetSegmentGenerator.ComputeOffsetSegment(seg, Positions.Right, this._distance, lineSegment2);
			double x = p1.X - p0.X;
			double num = Math.Atan2(p1.Y - p0.Y, x);
			switch (this._bufParams.EndCapStyle)
			{
			case EndCapStyle.Round:
				this._segList.AddPt(lineSegment.P1);
				this.AddFillet(p1, num + System.Math.PI*.5, num - System.Math.PI*.5, -1, this._distance);
				this._segList.AddPt(lineSegment2.P1);
				return;
			case EndCapStyle.Flat:
				this._segList.AddPt(lineSegment.P1);
				this._segList.AddPt(lineSegment2.P1);
				return;
			case EndCapStyle.Square:
			{
				Coordinate coordinate = new Coordinate();
				coordinate.X = System.Math.Abs(this._distance) * Math.Cos(num);
				coordinate.Y = System.Math.Abs(this._distance) * Math.Sin(num);
				Coordinate pt = new Coordinate(lineSegment.P1.X + coordinate.X, lineSegment.P1.Y + coordinate.Y);
				Coordinate pt2 = new Coordinate(lineSegment2.P1.X + coordinate.X, lineSegment2.P1.Y + coordinate.Y);
				this._segList.AddPt(pt);
				this._segList.AddPt(pt2);
				return;
			}
			default:
				return;
			}
		}

		private void AddMitreJoin(Coordinate p, LineSegment offset0, LineSegment offset1, double distance)
		{
			bool flag = true;
			Coordinate coordinate;
			try
			{
				coordinate = HCoordinate.Intersection(offset0.P0, offset0.P1, offset1.P0, offset1.P1);
				if (((distance <= 0.0) ? 1.0 : (coordinate.Distance(p) / System.Math.Abs(distance))) > this._bufParams.MitreLimit)
				{
					flag = false;
				}
			}
			catch (NotRepresentableException)
			{
				coordinate = new Coordinate(0.0, 0.0);
				flag = false;
			}
			if (flag)
			{
				this._segList.AddPt(coordinate);
				return;
			}
			this.AddLimitedMitreJoin(offset0, offset1, distance, this._bufParams.MitreLimit);
		}

		private void AddLimitedMitreJoin(LineSegment offset0, LineSegment offset1, double distance, double mitreLimit)
		{
			Coordinate p = this._seg0.P1;
			double num = AngleUtility.Angle(p, this._seg0.P0);
			double num2 = AngleUtility.AngleBetweenOriented(this._seg0.P0, p, this._seg1.P1) / 2.0;
			double num3 = AngleUtility.Normalize(AngleUtility.Normalize(num + num2) + System.Math.PI);
			double num4 = mitreLimit * distance;
			double num5 = num4 * System.Math.Abs(Math.Sin(num2));
			double num6 = distance - num5;
			double x = p.X + num4 * Math.Cos(num3);
			double y = p.Y + num4 * Math.Sin(num3);
			Coordinate p2 = new Coordinate(x, y);
			LineSegment lineSegment = new LineSegment(p, p2);
			Coordinate pt = lineSegment.PointAlongOffset(1.0, num6);
			Coordinate pt2 = lineSegment.PointAlongOffset(1.0, -num6);
			if (this._side == Positions.Left)
			{
				this._segList.AddPt(pt);
				this._segList.AddPt(pt2);
				return;
			}
			this._segList.AddPt(pt2);
			this._segList.AddPt(pt);
		}

		private void AddBevelJoin(LineSegment offset0, LineSegment offset1)
		{
			this._segList.AddPt(offset0.P1);
			this._segList.AddPt(offset1.P0);
		}

		private void AddFillet(Coordinate p, Coordinate p0, Coordinate p1, int direction, double radius)
		{
			double x = p0.X - p.X;
			double num = Math.Atan2(p0.Y - p.Y, x);
			double x2 = p1.X - p.X;
			double num2 = Math.Atan2(p1.Y - p.Y, x2);
			if (direction == -1)
			{
				if (num <= num2)
				{
					num += System.Math.PI*2;
				}
			}
			else if (num >= num2)
			{
				num -= System.Math.PI*2;
			}
			this._segList.AddPt(p0);
			this.AddFillet(p, num, num2, direction, radius);
			this._segList.AddPt(p1);
		}

		private void AddFillet(Coordinate p, double startAngle, double endAngle, int direction, double radius)
		{
			int num = (direction == -1) ? -1 : 1;
			double num2 = System.Math.Abs(startAngle - endAngle);
			int num3 = (int)(num2 / this._filletAngleQuantum + 0.5);
			if (num3 < 1)
			{
				return;
			}
			double num4 = num2 / (double)num3;
			double num5 = 0.0;
			Coordinate coordinate = new Coordinate();
			while (num5 < num2)
			{
				double num6 = startAngle + (double)num * num5;
				coordinate.X = p.X + radius * Math.Cos(num6);
				coordinate.Y = p.Y + radius * Math.Sin(num6);
				this._segList.AddPt(coordinate);
				num5 += num4;
			}
		}

		public void CreateCircle(Coordinate p)
		{
			Coordinate pt = new Coordinate(p.X + this._distance, p.Y);
			this._segList.AddPt(pt);
			this.AddFillet(p, 0.0, System.Math.PI*2, -1, this._distance);
			this._segList.CloseRing();
		}

		public void CreateSquare(Coordinate p)
		{
			this._segList.AddPt(new Coordinate(p.X + this._distance, p.Y + this._distance));
			this._segList.AddPt(new Coordinate(p.X + this._distance, p.Y - this._distance));
			this._segList.AddPt(new Coordinate(p.X - this._distance, p.Y - this._distance));
			this._segList.AddPt(new Coordinate(p.X - this._distance, p.Y + this._distance));
			this._segList.CloseRing();
		}

		private const double OffsetSegmentSeparationFactor = 0.001;

		private const double InsideTurnVertexSnapDistanceFactor = 0.001;

		private const double CurveVertexSnapDistanceFactor = 1E-06;

		private const int MaxClosingSegLenFactor = 80;

		private double _maxCurveSegmentError;

		private readonly double _filletAngleQuantum;

		private readonly int _closingSegLengthFactor = 1;

		private OffsetSegmentString _segList;

		private double _distance;

		private readonly IPrecisionModel _precisionModel;

		private readonly IBufferParameters _bufParams;

		private readonly LineIntersector _li;

		private Coordinate _s0;

		private Coordinate _s1;

		private Coordinate _s2;

		private readonly LineSegment _seg0 = new LineSegment();

		private readonly LineSegment _seg1 = new LineSegment();

		private readonly LineSegment _offset0 = new LineSegment();

		private readonly LineSegment _offset1 = new LineSegment();

		private Positions _side;

		private bool _hasNarrowConcaveAngle;
	}
}
